#region License
// Product:
// MCM (Message-Command-Message)
// mcm-framework.net
// 
// Author:
// Pawel Idzikowski
// Cologne, Germany
// idzikowski@polenter.com
// 
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the author(s) be held liable for any damages arising from
// the use of this software.
// 
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 
//     1. The origin of this software must not be misrepresented; you must not
//       claim that you wrote the original software. If you use this software
//       in a product, an acknowledgment in the product documentation would be
//       appreciated but is not required.
// 
//     2. Altered source versions must be plainly marked as such, and must not
//       be misrepresented as being the original software.
// 
//     3. This notice may not be removed or altered from any source distribution.
// 
#endregion
using System;
using System.Collections.Generic;
using System.Threading;

namespace Polenter.Mcm.Advanced
{
    /// <summary>
    /// Items are enqued on any thread, but dequeing runs
    /// on an intenal thread. All items are dequed one after another
    /// on the internal thread.
    /// </summary>
    /// <typeparam name="TItem"></typeparam>
    public class AsyncQueue<TItem> : IDisposable
    {
        ///<summary>
        /// Default name for the trace log
        ///</summary>
        public const string TraceLogName = "AsyncQueue";

        private readonly McmLog _log;
        private readonly string _name;
        private readonly IQueue _syncQueue;
        private readonly ManualResetEvent _waitHandle = new ManualResetEvent(true);
        private volatile bool _stopRequested;
        private volatile Thread _workingThread;

        ///<summary>
        ///</summary>
        ///<param name="name">is used for logging</param>
        ///<exception cref="ArgumentNullException"></exception>
        public AsyncQueue(string name)
        {
            if (name == null) throw new ArgumentNullException("name");
            _name = name;
            _syncQueue = new SimpleQueue();
            _log = new McmLog(TraceLogName);
        }

        ///<summary>
        ///</summary>
        ///<param name="name">is used for logging</param>
        ///<param name="syncqueue">internal queue, eg. MSMQ or a persisted one</param>
        ///<exception cref="ArgumentNullException"></exception>
        public AsyncQueue(string name, IQueue syncqueue)
        {
            if (name == null) throw new ArgumentNullException("name");
            _name = name;
            _syncQueue = syncqueue ?? new SimpleQueue();
            _log = new McmLog(TraceLogName);
        }

        ///<summary>
        /// Is waiting for new items
        ///</summary>
        public bool IsWorking
        {
            get { return _workingThread != null; }
        }

        ///<summary>
        /// Name is used for logging and for debugging
        ///</summary>
        public string Name
        {
            get { return _name; }
        }

        ///<summary>
        /// Stop() was executed
        ///</summary>
        public bool StopRequested
        {
            get { return _stopRequested; }
        }

        #region IDisposable Members

        /// <summary>
        /// 
        /// </summary>
        public void Dispose()
        {
            Stop();
        }

        #endregion

        /// <summary>
        /// Adds item to the queue asynchronously
        /// </summary>
        /// <param name="item"></param>
        public void Enqueue(TItem item)
        {
            if (Equals(item, default(TItem))) throw new ArgumentNullException("item");
            _log.WriteInfo(string.Format("{0}.Enqueue({1})", _name, item));
            throwIfNotWorking();
            enqueue(item);
        }

        /// <summary>
        /// Clear the queue
        /// </summary>
        /// <returns>what items have been cleared</returns>
        public TItem[] Clear()
        {
            _log.WriteInfo(string.Format("{0}.Clear()", _name));
            lock (_syncQueue)
            {
                TItem[] items = ToArray();
                _syncQueue.Clear();
                return items;
            }
        }

        /// <summary>
        /// Get all items as an array. Is multithread secure
        /// </summary>
        /// <returns></returns>
        public TItem[] ToArray()
        {
            lock (_syncQueue)
            {
                var result = new List<TItem>();
                foreach (TItem item in _syncQueue.ToArray())
                {
                    result.Add(item);
                }
                return result.ToArray();
            }
        }

        private void throwIfNotWorking()
        {
            if (!IsWorking)
            {
                throw new InvalidOperationException(
                    string.Format("Queue \"{0}\" is not working. Please start the queue.", _name));
            }
        }

        private void throwIfWorking()
        {
            if (IsWorking)
            {
                throw new InvalidOperationException(string.Format("Queue \"{0}\" is already working.", _name));
            }
        }

        /// <summary>
        /// Starts the work
        /// </summary>
        public void Start()
        {
            _log.WriteInfo(string.Format("{0}.Start()", _name));
            throwIfWorking();

            _workingThread = new Thread(doWork);
            _workingThread.Name = _name;
            _workingThread.IsBackground = false;
            _workingThread.Start();
        }

        private void doWork()
        {
            _stopRequested = false;
            while (!_stopRequested)
            {
                TItem item = dequeue();
                while (!Equals(item, default(TItem)))
                {
                    invokeItemDequeued(new AsyncQueueEventArgs<TItem>(item));
                    item = dequeue();
                }

                if (!_stopRequested)
                {
                    _log.WriteVerbose(string.Format("{0} is sleeping.", _name));
                    _waitHandle.Reset();
                    _waitHandle.WaitOne();
                    _log.WriteVerbose(string.Format("{0} is awake.", _name));
                }
            }
            _log.WriteInfo(string.Format("{0} is stopped.", _name));
            _workingThread = null;
        }

        private void enqueue(TItem item)
        {
            lock (_syncQueue)
            {
                _syncQueue.Enqueue(item);
                _waitHandle.Set();
            }
        }

        private TItem dequeue()
        {
            lock (_syncQueue)
            {
                if (_syncQueue.Count == 0)
                {
                    return default(TItem);
                }
                return (TItem) _syncQueue.Dequeue();
            }
        }

        /// <summary>
        /// Stops the queue asynchronously
        /// </summary>
        public void Stop()
        {
            _log.WriteInfo(string.Format("{0}.Close()", _name));
            _stopRequested = true;
            _waitHandle.Set();
        }

        ///<summary>
        /// Invoked when an item is dequed
        ///</summary>
        public event AsyncQueueEventHandler<TItem> ItemDequeued;

        private void invokeItemDequeued(AsyncQueueEventArgs<TItem> args)
        {
            _log.WriteInfo(string.Format("{0}.ItemDequeued({1})", _name, args.Item));
            AsyncQueueEventHandler<TItem> handler = ItemDequeued;
            if (handler != null) handler(this, args);
        }
    }
}